﻿using System;
using System.Collections.Generic;
using CoreEx.Common.Ioc;
using CoreEx.Common.Proxy;
using CoreEx.DynamicInvoker;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using LinFu.Proxy.Interfaces;
using Mono.Cecil;
using CoreEx.Common.Extensions;

namespace CoreEx.Common.Implementation.Ioc
{
    /// <summary>
    /// Modifies a <see cref="Type"/> 
    /// by implementing one or more additional interfaces.
    /// </summary>
    [Implements(typeof(IImplementor))]
    public class Implementor : IImplementor, IInitialize
    {
        private IServiceContainer _serviceContainer;

        /// <summary>
        /// Gets or sets the <see cref="IExtractInterfaces"/> instance 
        /// that will be used to determine the list of interfaces that should be implemented.
        /// </summary>
        public IExtractInterfaces InterfaceExtractor { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="IMethodPicker"/> instance that 
        /// will be used to determine the methods that should be implemented.
        /// </summary>
        public IMethodPicker MethodPicker { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="IMethodBuilder"/> instance that 
        /// will be used to build the methods.
        /// </summary>
        public IMethodBuilder MethodBuilder { get; set; }

        /// <summary>
        /// Modifies the <paramref name="baseType"/> to implement a list of additional <paramref name="interfaces"/>.
        /// </summary>
        /// <param name="baseType">The type that will be modified</param>
        /// <param name="interfaces">The list of interfaces to implement.</param>
        public void Implement(TypeDefinition baseType, Type[] interfaces)
        {
            var actualInterfaces = new HashSet<Type>(interfaces);

            interfaces.ForEach(i => InterfaceExtractor.GetInterfaces(i, actualInterfaces));

            foreach (var actualInterface in actualInterfaces)
            {
                baseType.Interfaces.Add(baseType.Module.Import(actualInterface));
                var methods = MethodPicker.ChooseProxyMethodsFrom(actualInterface, new Type[] { });

                //Find a method body emitter that can implement the methods exposed from the current interface.
                MethodBuilder.GetType().DynamicInvoke(MethodBuilder, "Emitter",
                                                      _serviceContainer.GetService<IMethodBodyEmitter>(actualInterface.Name, baseType));
                //Create the methods
                methods.ForEach(m => MethodBuilder.CreateMethod(baseType, m));

            }
        }

        public virtual void Initialize(IServiceContainer source)
        {
            _serviceContainer = source;
            InterfaceExtractor = _serviceContainer.GetService<IExtractInterfaces>();
            MethodPicker = _serviceContainer.GetService<IMethodPicker>();
            MethodBuilder = _serviceContainer.GetService<IMethodBuilder>("ProxyMethodBuilder");
            if (typeof(IActualTarget).IsAssignableFrom(MethodBuilder.GetType()))
            {
                MethodBuilder = (IMethodBuilder)((IActualTarget) MethodBuilder).Instance;
            }
        }
    }
}
